package org.yfl.mbg.enhance;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.mybatis.generator.plugins.MapperAnnotationPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yfl.mbg.enhance.constants.CommonConstants;
import org.yfl.mbg.enhance.enums.OrmFrameworkEnum;
import org.yfl.mbg.enhance.plugin.JavaDocPlugin;
import org.yfl.mbg.enhance.plugin.GenDtoPlugin;
import org.yfl.mbg.enhance.plugin.GenJpaClassPlugin;
import org.yfl.mbg.enhance.plugin.GenMybatisClassPlugin;
import org.yfl.mbg.enhance.utils.ClassUtil;
import org.yfl.mbg.enhance.utils.DbDriverUtil;
import org.yfl.mbg.enhance.utils.FileUtil;
import org.yfl.mbg.enhance.utils.JsonUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 代码生成器
 * @author yanfeilin
 * @version 1.0.0
 * @date 2020/5/18 22:33
 */
public class CodeGenerator {

    private final Logger LOG = LoggerFactory.getLogger(getClass());

    private Properties properties;

    private String driverJarName;

    public CodeGenerator() {
        InputStream inputStream = null;
        try{
            inputStream = CodeGenerator.class.getResourceAsStream(CommonConstants.CONFIG_FILE_NAME);
            properties = new Properties();
            properties.load(inputStream);

            driverJarName = properties.getProperty("generator.dbDriverJar");
        }catch (IOException e){
            LOG.error("加载配置文件{}失败",CommonConstants.CONFIG_FILE_NAME,e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                }catch (IOException e){
                    LOG.error("文件流关闭发生错误!",e);
                }
            }
        }

    }

    /**
     * 根据数据库表名和domain类名生成代码
     * @param tableAndDomainMap 数据库表名和domain类名map
     * @param moduleName 模块明
     * @throws Exception
     */
    public void genCodeFromTable(Map<String,String> tableAndDomainMap,String moduleName) throws Exception {
        List<String> warnings = new ArrayList<>();
        boolean overwrite = true;
        Configuration config = new Configuration();

        //从classpath获取数据库驱动包
        config.addClasspathEntry(FileUtil.getDbJarFilePath(driverJarName));

        Context genContext = getContext(tableAndDomainMap,moduleName);
        config.addContext(genContext);

        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
        LOG.warn("{}",warnings);
    }

    private Context getContext(Map<String,String> tableAndDomainMap,String moduleName){
        Context genContext = new Context(ModelType.HIERARCHICAL);
        genContext.setId("genContext");
        genContext.setJava8Targeted(true);
        genContext.setTargetRuntime("MyBatis3");
        genContext.setJdbcConnectionConfiguration(getJdbcConnectionConfig());

        addPluginConfig(genContext,moduleName);

        addTableConfig(genContext,tableAndDomainMap);

        genContext.setJavaTypeResolverConfiguration(getJavaTypeResolverConfig());

        genContext.setJavaModelGeneratorConfiguration(getJavaModelGeneratorConfig(moduleName));

        genContext.setJavaClientGeneratorConfiguration(getClientGeneratorConfig(moduleName));

        genContext.setSqlMapGeneratorConfiguration(getMybatisXmlGeneratorConfig(moduleName));

        genContext.setCommentGeneratorConfiguration(getCommnetGeneratorConfig());

        contextAddProperties(genContext,moduleName);

        return genContext;
    }

    private void contextAddProperties(Context context,String moduleName){
        for (Map.Entry<Object, Object> tempEntry : properties.entrySet()) {
            String key = (String) tempEntry.getKey();
            String value = (String) tempEntry.getValue();
            if("generator.dto.targetProject".equals(key)){
                if(StringUtils.isNotEmpty(value)){
                    value = FileUtil.getSameLevelProjectByUserDir(value);
                    value += CommonConstants.MAIN_SOURCE_DIR;
                }
            }
            context.addProperty(key, value);
        }
        context.addProperty("moduleName",moduleName);
    }

    /**
     * 新增插件配置
     * @param context
     */
    protected void addPluginConfig(Context context,String moduleName){
        String ormFramework = properties.getProperty("generator.ormframework");
        OrmFrameworkEnum ormFrameworkEnum = null;
        if(StringUtils.isEmpty(ormFramework)){
          ormFrameworkEnum = ClassUtil.deduceOrmFramework();
        }else {
            ormFrameworkEnum = OrmFrameworkEnum.valueOf(ormFramework);
        }

        PluginConfiguration clsAddJavaDocPluginConfig = new PluginConfiguration();
        clsAddJavaDocPluginConfig.setConfigurationType(JavaDocPlugin.class.getName());
        context.addPluginConfiguration(clsAddJavaDocPluginConfig);

        switch (ormFrameworkEnum){
            case MYBATIS:
                PluginConfiguration genMybatisPluginConfig = new PluginConfiguration();
                genMybatisPluginConfig.setConfigurationType(GenMybatisClassPlugin.class.getName());
                context.addPluginConfiguration(genMybatisPluginConfig);

                PluginConfiguration mapperAnnoPluginConfig = new PluginConfiguration();
                mapperAnnoPluginConfig.setConfigurationType(MapperAnnotationPlugin.class.getName());
                context.addPluginConfiguration(mapperAnnoPluginConfig);

                break;
            case JPA:
                PluginConfiguration jpaGenPluginConfig = new PluginConfiguration();
                jpaGenPluginConfig.setConfigurationType(GenJpaClassPlugin.class.getName());
                context.addPluginConfiguration(jpaGenPluginConfig);
                break;
        }


        //新增dto生成插件
        PluginConfiguration dtoPluginConfig = new PluginConfiguration();
        dtoPluginConfig.setConfigurationType(GenDtoPlugin.class.getName());
        dtoPluginConfig.addProperty("moduleName",moduleName);
        context.addPluginConfiguration(dtoPluginConfig);
    }

    protected JavaClientGeneratorConfiguration getClientGeneratorConfig(
            String moduleName){
        JavaClientGeneratorConfiguration daoGeneratorConfig = new JavaClientGeneratorConfiguration();
        String daoTargetProjectName = properties.getProperty("generator.dao.targetProject");
        String daoTargetPkg = properties.getProperty("generator.dao.targetPkg");
        if(StringUtils.isEmpty(daoTargetPkg)){
            throw new IllegalArgumentException("配置项[generator.dao.targetPkg]不能为空!");
        }
        String daoTargetProjectPath = SystemUtils.USER_DIR;
        if(StringUtils.isNotEmpty(daoTargetProjectName)){
            daoTargetProjectPath = FileUtil.getSameLevelProjectByUserDir(daoTargetProjectName);
        }
        daoGeneratorConfig.setTargetProject(daoTargetProjectPath+CommonConstants.MAIN_SOURCE_DIR);
        if(StringUtils.isNotEmpty(moduleName)) {
            daoTargetPkg = daoTargetPkg.concat("." + moduleName);
        }
        daoGeneratorConfig.setTargetPackage(daoTargetPkg);
        //daoGeneratorConfig.setConfigurationType("ANNOTATEDMAPPER");
        daoGeneratorConfig.setConfigurationType("XMLMAPPER");
        daoGeneratorConfig.addProperty("enableSubPackages","true");
        return daoGeneratorConfig;
    }

    protected SqlMapGeneratorConfiguration getMybatisXmlGeneratorConfig(String moduleName){
        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();

        String sqlMapTargetProject = properties.getProperty("enerator.mapperxml.targetProject");
        String sqlMapTargetPkg = properties.getProperty("generator.mapperxml.targetPkg");
        if(StringUtils.isEmpty(sqlMapTargetPkg)){
            throw new IllegalArgumentException("配置项[generator.mapperxml.targetPkg]不能为空!");
        }

        String sqlMapTargetProjectPath = SystemUtils.USER_DIR;
        if(StringUtils.isNotEmpty(sqlMapTargetProject)){
            sqlMapTargetProjectPath = FileUtil.getSameLevelProjectByUserDir(sqlMapTargetProject);
        }
        sqlMapGeneratorConfiguration.setTargetProject(sqlMapTargetProjectPath+CommonConstants.MAIN_RESOURCE_DIR);
        if(StringUtils.isNotEmpty(moduleName)) {
            sqlMapTargetPkg = sqlMapTargetPkg.concat("." + moduleName);
        }
        sqlMapGeneratorConfiguration.setTargetPackage(sqlMapTargetPkg);
        sqlMapGeneratorConfiguration.addProperty("enableSubPackages","true");

        return sqlMapGeneratorConfiguration;
    }

    protected CommentGeneratorConfiguration getCommnetGeneratorConfig(){
        CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();
        return commentGeneratorConfiguration;
    }

    /**
     * domain生成器配置
     * @return
     */
    protected JavaModelGeneratorConfiguration getJavaModelGeneratorConfig(String moduleName){
        JavaModelGeneratorConfiguration javaModelGenConfig = new JavaModelGeneratorConfiguration();

        String javaModelProjectName = properties.getProperty("generator.domain.targetProject");
        //默认为当前工程
        String targetProjectPath = SystemUtils.USER_DIR;
        if(StringUtils.isNotEmpty(javaModelProjectName)){
            targetProjectPath = FileUtil.getSameLevelProjectByUserDir(
                    javaModelProjectName);
        }
        String javaModelPkg = properties.getProperty("generator.domain.targetPkg");
        if(StringUtils.isEmpty(javaModelPkg)){
            throw  new IllegalArgumentException("配置项[generator.domain.targetPkg]不能为空!");
        }
        javaModelGenConfig.setTargetProject(targetProjectPath+CommonConstants.MAIN_SOURCE_DIR);
        if(StringUtils.isNotEmpty(moduleName)) {
            javaModelPkg = javaModelPkg.concat("." + moduleName);
        }
        javaModelGenConfig.setTargetPackage(javaModelPkg);

        javaModelGenConfig.addProperty("enableSubPackages","true");
        javaModelGenConfig.addProperty("trimStrings","true");

        return javaModelGenConfig;
    }

    protected JavaTypeResolverConfiguration getJavaTypeResolverConfig(){
        JavaTypeResolverConfiguration typeResolverConfiguration = new
                JavaTypeResolverConfiguration();
        typeResolverConfiguration.addProperty("forceBigDecimals","true");
        return typeResolverConfiguration;
    }

    protected JDBCConnectionConfiguration getJdbcConnectionConfig(){
        JDBCConnectionConfiguration jdbcConnectionConfiguration = new
                JDBCConnectionConfiguration();
        String username = properties.getProperty("generator.jdbc.username");
        String jdbcUrl = properties.getProperty("generator.jdbc.url");
        String pwd = properties.getProperty("generator.jdbc.password");
        String driverClass = properties.getProperty("generator.jdbc.driverClass");
        if(StringUtils.isEmpty(driverClass)) {//如果driverClass没配置，根据jdbc url推断
            driverClass = DbDriverUtil.deduceDriverClass(jdbcUrl);
        }
        jdbcConnectionConfiguration.setUserId(username);
        jdbcConnectionConfiguration.setPassword(pwd);
        jdbcConnectionConfiguration.setConnectionURL(jdbcUrl);
        jdbcConnectionConfiguration.setDriverClass(driverClass);

        String otherPropertiesStr = properties.getProperty("generator.jdbc.otherProperties");
        if(StringUtils.isNotEmpty(otherPropertiesStr)) {
            Map<String, String> otherProperties = JsonUtil.jsonString2obj(otherPropertiesStr,Map.class);
            for (Map.Entry<String, String> tempEntry : otherProperties.entrySet()) {
                jdbcConnectionConfiguration.addProperty(tempEntry.getKey(),tempEntry.getValue());
            }
        }
        return jdbcConnectionConfiguration;
    }

    protected void addTableConfig(Context context,Map<String,String> tableAndDomainMap){
        TableConfiguration tableConfiguration = null;

        for (Map.Entry<String, String> tempEntry : tableAndDomainMap.entrySet()) {
            tableConfiguration = new TableConfiguration(context);
            String tableName = tempEntry.getKey();
            String domainName = tempEntry.getValue();
            tableConfiguration.setTableName(tableName);
            tableConfiguration.setDelimitIdentifiers(true);
            tableConfiguration.setAllColumnDelimitingEnabled(true);
            tableConfiguration.setDomainObjectName(domainName);
            tableConfiguration.setConfiguredModelType("flat");
            tableConfiguration.addProperty("useActualColumnNames","true");
            tableConfiguration.setSqlProviderName(domainName+"Provider");
            boolean exampleEnable = false;
            String exampleEnableStr = properties.getProperty("generator.exampleQuery.enable");
            if(StringUtils.isNotEmpty(exampleEnableStr)) {
                exampleEnable = Boolean.parseBoolean(exampleEnableStr);
            }

            if(!exampleEnable) {//关闭example生成
                tableConfiguration.setCountByExampleStatementEnabled(false);
                tableConfiguration.setDeleteByExampleStatementEnabled(false);
                tableConfiguration.setUpdateByExampleStatementEnabled(false);
                tableConfiguration.setSelectByExampleStatementEnabled(false);
            }

            //添加忽略的表格列
            String ignoreColumnsStr = properties.getProperty("generator.ignoreColumns");
            if(StringUtils.isNotEmpty(ignoreColumnsStr)){
                String[] ignoreColumnNameArr = ignoreColumnsStr.split(",");
                IgnoredColumn ignoredColumn = null;
                for (String ignoreColumnName : ignoreColumnNameArr) {
                     ignoredColumn = new IgnoredColumn(ignoreColumnName);
                    tableConfiguration.addIgnoredColumn(ignoredColumn);
                }
            }

            //设置domain字段重命名规则
            String colRenamingRuleStr = properties.getProperty("generator.columnRenamingRule");
            if(StringUtils.isNotEmpty(colRenamingRuleStr)){
                String[] colRenamingRuleArr = colRenamingRuleStr.split(",");
                ColumnRenamingRule renamingRule = new ColumnRenamingRule();
                renamingRule.setSearchString(colRenamingRuleArr[0]);
                renamingRule.setReplaceString(colRenamingRuleArr[1]);
                tableConfiguration.setColumnRenamingRule(renamingRule);
            }

            //修改dao和xml文件名后缀
            String daoSuffix = properties.getProperty("generator.dao.suffix");
            if (StringUtils.isNotEmpty(daoSuffix)){
                tableConfiguration.setMapperName(domainName+daoSuffix);
            }

            context.addTableConfiguration(tableConfiguration);
        }
    }
}
